/*
 * The Smacker library was ported from C# for use in Silvie. The original
 * library is copyright Chris Toshok. It is released under an X11 license.
 */

package utils.smacker;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;

import utils.DataReader;

public class SmackerFile {
	private SmackerHeader header;
	private int[] frameSizes;
	private byte[] frameTypes;
	private boolean isV4;
	private BigHuffmanTree mMap;
	private BigHuffmanTree mClr;
	private BigHuffmanTree full;
	private BigHuffmanTree type;
	private RandomAccessFile stream;

	public SmackerHeader getHeader() {
		return this.header;
	}

	public int[] getFrameSizes() {
		return this.frameSizes;
	}

	public byte[] getFrameTypes() {
		return this.frameTypes;
	}

	public boolean isV4() {
		return this.isV4;
	}

	public BigHuffmanTree getmMap() {
		return this.mMap;
	}

	public BigHuffmanTree getmClr() {
		return this.mClr;
	}

	public BigHuffmanTree getFull() {
		return this.full;
	}

	public BigHuffmanTree getType() {
		return this.type;
	}

	public RandomAccessFile getStream() {
		return this.stream;
	}

	private static SmackerHeader readHeader(RandomAccessFile s) throws IOException {
		SmackerHeader smk = new SmackerHeader();
		int i;
		/* read and check header */
		smk.signature = DataReader.readInt(s);
		if (smk.signature != makeTag('S', 'M', 'K', '2') && smk.signature != makeTag('S', 'M', 'K', '4')) {
			throw new IOException("Not an SMK stream");
		}
		smk.width = DataReader.readInt(s);
		smk.height = DataReader.readInt(s);
		smk.nbFrames = DataReader.readInt(s);
		smk.ptsInc = DataReader.readInt(s);
		smk.fps = calcFps(smk);
		smk.flags = DataReader.readInt(s);
		for (i = 0; i < 7; i++) {
			smk.audioSize[i] = DataReader.readInt(s);
		}
		smk.treesSize = DataReader.readInt(s);
		smk.nMapSize = DataReader.readInt(s);
		smk.mClrSize = DataReader.readInt(s);
		smk.fullSize = DataReader.readInt(s);
		smk.typeSize = DataReader.readInt(s);
		for (i = 0; i < 7; i++) {
			smk.audioRate[i] = DataReader.readInt(s);
		}
		smk.dummy = DataReader.readInt(s);
		/* setup data */
		if (smk.nbFrames > 0xFFFFFF) {
			throw new IOException("Too many frames: " + smk.nbFrames);
		}
		return smk;
	}

	private static double calcFps(SmackerHeader smk) {
		if (smk.ptsInc > 0) {
			return 1000.0 / smk.ptsInc;
		} else if (smk.ptsInc < 0) {
			return 100000.0 / (-smk.ptsInc);
		} else {
			return 10.0;
		}
	}

	public static SmackerFile openFromStream(RandomAccessFile s) throws IOException {
		int i;
		SmackerFile file = new SmackerFile();
		file.header = readHeader(s);
		int nbFrames = file.header.nbFrames;
		// The ring frame is not counted!
		if (file.header.hasRingFrame()) {
			nbFrames++;
		}
		file.frameSizes = new int[nbFrames];
		file.frameTypes = new byte[nbFrames];
		file.isV4 = (file.header.signature != makeTag('S', 'M', 'K', '2'));
		/* read frame info */
		for (i = 0; i < nbFrames; i++) {
			file.frameSizes[i] = DataReader.readInt(s);
		}
		for (i = 0; i < nbFrames; i++) {
			file.frameTypes[i] = (byte) s.read();
		}
		// The rest of the header is a bitstream
		SmackerBitStream m = new SmackerBitStream(s);
		// Read huffman trees
		// MMap
		// System.Console.WriteLine("Mono map tree");
		file.mMap = new BigHuffmanTree();
		file.mMap.buildTree(m);
		// MClr (color map)
		// System.Console.WriteLine("Mono Color tree");
		file.mClr = new BigHuffmanTree();
		file.mClr.buildTree(m);
		// Full (full block stuff)
		// System.Console.WriteLine("Full tree");
		file.full = new BigHuffmanTree();
		file.full.buildTree(m);
		// Type (full block stuff)
		// System.Console.WriteLine("Type descriptor tree");
		file.type = new BigHuffmanTree();
		file.type.buildTree(m);
		// We are ready to decode frames
		file.stream = s;
		return file;
	}

	// Returns a decoder for this Smackerfile
	public SmackerDecoder getDecoder() {
		return new SmackerDecoder(this);
	}

	public static int makeTag(char... chars) {
		byte[] bytes = new byte[4];
		int i = 0;
		for (char c : chars) {
			bytes[i++] = (byte) c;
		}
		return ByteBuffer.wrap(bytes).order(java.nio.ByteOrder.LITTLE_ENDIAN).getInt();
	}
}
